package aos.voting;

import java.net.InetAddress;
import java.util.Random;

import aos.voting.entity.Configuration;
import aos.voting.entity.GlobalVariables;
import aos.voting.entity.Message;
import aos.voting.entity.MessageType;
import aos.voting.exception.AosException;
import aos.voting.log.LogUtils;

public class ClientNode extends NetworkNode {
	protected VotingTree voting;
	protected Object synObject;
	protected int msgId;
	protected int commitId;
	protected int commitValue;
	protected int commitVersion;
	protected int success;
	protected int failure;

	public ClientNode(Configuration config) throws AosException {
		super(config);
		voting = new VotingTree(config.getNS());
		synObject = new Object();
		msgId = 1;
		commitId = -1;
		commitValue = 0;
		commitVersion = -1;
		success = 0;
		failure = 0;
	}

	public Object getSysObject() {
		return synObject;
	}

	public boolean isGranted() {
		return voting.isGranted();
	}

	public void request(int objectId, int value) throws AosException {
		commitId = objectId;
		voting.clear();
		for (int serverId = 0; serverId < config.getNS(); ++serverId) {
			Message msg = new Message(MessageType.REQ, serverId, nodeId,
					objectId, value);
			msg.setMsgId(msgId);
			sendMessage(msg);
		}
	}

	public void withdraw() throws AosException {
		for (int serverId = 0; serverId < config.getNS(); ++serverId) {
			Message msg = new Message(MessageType.WITHDRAW, serverId, nodeId);
			msg.setMsgId(msgId);
			sendMessage(msg);
		}
		failure++;
		msgId++;
		voting.clear();
	}

	public void commit() throws AosException {
		for (int serverId = 0; serverId < config.getNS(); ++serverId) {
			Message msg = new Message(MessageType.COMMIT, serverId, nodeId,
					commitId, commitValue, commitVersion);
			msg.setMsgId(msgId);
			sendMessage(msg);
		}
		success++;
		msgId++;
		voting.clear();
	}

	public void dumpReport() {
		LogUtils.keyLog(report.toString() + "Success: " + success
				+ " Failure: " + failure);
	}

	public void deactivate() throws AosException {
		for (int serverId : config.getFailNodes()) {
			Message msg = new Message(MessageType.DEACTIVATE, serverId, nodeId);
			sendMessage(msg);
		}
	}

	public void reactivate() throws AosException {
		for (int serverId : config.getFailNodes()) {
			Message msg = new Message(MessageType.REACTIVATE, serverId, nodeId);
			sendMessage(msg);
		}
	}

	protected void handleMessageReceive(Message msg) throws AosException {
		LogUtils.debugLog("handleMessageReceive: " + msg.toString());
		switch (msg.getMsgType()) {
		case GRANT:
			handleGrant(msg);
			break;
		case ACK:
			handleAck(msg);
			break;
		default:
		}
	}

	protected void handleGrant(Message msg) {
		synchronized (synObject) {
			if (msg.getMsgId() < msgId) {
				LogUtils.debugLog("message out of date, ignore: "
						+ msg.toString());
				return;
			}
			if (commitVersion < msg.getVersion()) {
				commitVersion = msg.getVersion();
				commitValue = msg.getValue();
			}
			voting.grant(msg.getFromId());
			if (isGranted()) {
				synObject.notify();
			}
		}
	}

	protected void handleAck(Message msg) {
		synchronized (synObject) {
			// if (msg.getMsgId() < msgId) {
			// LogUtils.debugLog("message out of date, ignore: "
			// + msg.toString());
			// return;
			// }
		}
	}

	public static void main(String args[]) throws Exception {
		GlobalVariables.initialize("global.txt");
		String logFile = null;
		if (GlobalVariables.logfile != null
				&& !GlobalVariables.logfile.trim().equals("")) {
			logFile = InetAddress.getLocalHost().getHostName() + "_"
					+ GlobalVariables.logfile;
		}
		LogUtils.initialize(GlobalVariables.loglevel, logFile,
				GlobalVariables.logAppend);
		Configuration config = new Configuration(GlobalVariables.input);

		ClientNode node = new ClientNode(config);
		new Thread(node).start();

		int nodeId = node.getNodeId();
		Random ran = new Random();
		long minTime = Long.MAX_VALUE, maxTime = Long.MIN_VALUE, totalTime = 0;
		int successCnt = 0;
		for (int i = 0; i < config.getM(); ++i) {
			if (i == config.getM() / 5 && nodeId == config.getNS()) {
				node.deactivate();
			} else if (i == config.getM() * 2 / 5 && nodeId == config.getNS()) {
				node.reactivate();
			}

			// uniform distribution
			long sleepTime = (long) ((ran.nextDouble()
					* (GlobalVariables.maxSleep - GlobalVariables.minSleep) + GlobalVariables.minSleep) * config
					.getTimeUnit());
			LogUtils.debugLog("sleepTime: " + sleepTime);
			if (sleepTime > 0) {
				Thread.sleep(sleepTime);
			}
			int objectId = ran.nextInt(GlobalVariables.objectNum);
			LogUtils.debugLog("objectId: " + objectId);

			// send write requests
			node.request(objectId, 1);

			// wait for timeout or hold for some time
			Object synObject = node.getSysObject();
			synchronized (synObject) {
				long timeout = (long) (GlobalVariables.awaitingGrant * config
						.getTimeUnit());
				long startReq = System.currentTimeMillis();
				synObject.wait(timeout);
				if (node.isGranted()) {
					long endReq = System.currentTimeMillis();
					successCnt++;
					minTime = Math.min(minTime, endReq - startReq);
					maxTime = Math.max(maxTime, endReq - startReq);
					totalTime += endReq - startReq;
					Thread.sleep((long) (GlobalVariables.holdTime * config
							.getTimeUnit()));
					node.commit();
				} else {
					node.withdraw();
				}
			}
		}
		Thread.sleep(10000);
		node.dumpReport();
		LogUtils.keyLog("minTime: " + minTime + " maxTime: " + maxTime
				+ " avgTime: " + totalTime / successCnt);
	}
}
